/**
 * 与 h5 的区别
 * - 初始化：
 *    - 需要在 onLoad() 或 mounted() 之后进行初始化，否则获取不到路由信息
 *    - 在组件内用 provide 注入 tracker 时无法透传到深层组件，传递的也不是响应式对象，因此需要在 page 中完成注入（toutiao）
 *    - 需要使用 useInitResponsiveProvideMixin 和 useGetResponsiveProvideMixin 实现深层注入
 *
 * - 参数：
 *    - 不支持在 data-track-params 中以选择器形式获取其他元素的值
 *    - 小程序中设置计数器，需要在 new Tracker() 时传递 counterOptions，无法自动识别 data-track-counter
 *
 * - 事件：
 *    - 绑定 click 事件无效时，检查元素上是否有绑定 click 或其他点击类型的事件，没有的话绑定一个空方法即可 @click="() => {}"
 *    - 滚动事件需要手动调用 tracker.handleScroll(scrollTop)  并将滚动高度传入
 *    - 元素曝光事件，需要手动在页面中调用 addListenerElementView() 方法来设置
 */

import {TrackerQueue} from './TrackerQueue'
import {getCurrentPageInfo} from '@/services/common/Device'
import {getDynamicParams, getPerformanceInfo} from './TrackerUtils'

// 元素曝光监听器
const elementViewObserverMap = {}

/**
 * 自动化埋点支持的事件
 */
export const TrackEvent = {
  pageView: 'pageview', // 页面曝光
  pageLeave: 'pageleave', // 离开页面
  click: 'trackClick', // 元素点击
  scroll: 'scroll', // 页面滚动
  areaVisit: 'areavisit', // 元素区域访问，点击区域时触发 entry，离开区域时触发 leave，记录访问时长
  elementView: 'elementview' // 元素曝光，曝光时触发一次 view，隐藏时触发 leave，记录曝光时长
}

/**
 * 后端接收的事件名
 */
export const TrackEventBackend = {
  pageView: 'page_view', // 页面曝光
  pageLoad: 'page_load', // 页面加载完毕
  pageLeave: 'page_leave', // 离开页面
  pageScroll: 'page_scroll', // 页面滚动
  pageActiveStop: 'page_active_stop', // 页面停止活动
  pageActiveRestart: 'page_active_restart', // 页面重新开始活动
  areaVisitEntry: 'area_visit_entry', // 元素区域访问 entry
  areaVisitLeave: 'area_visit_leave', // 元素区域访问 leave
  elementView: 'element_view', // 元素曝光
  elementHide: 'element_hide', // 元素隐藏
  click: 'click', // 点击
  log: 'log' // 日志
}

/**
 * 埋点类
 * 初始化时需要异步获取数据
 */
export class Tracker {
  // 基础信息
  basicParams = {
    user_info: {},
    device_info: {}
  }
  customParams = {} // 自定义信息
  customFnMap = {} // 获取自定义参数方法
  trackerQueue = null // 埋点数据队列，定时上报
  areaVisitEntryMap = {} // 元素区域访问 map
  elementViewMap = {} // 元素曝光 map
  counterMap = {}
  counterKey = 'count'
  destroyFlag = false // 是否已销毁
  options = {}
  pageLoadParams = null // pageLoad 事件外部数据
  performanceInfo = null // 性能相关数据
  scrollInfo = {
    timer: null,
    flag: true,
    scrollTop: 0, // 上次滚动高度
    duration: 100, // 100ms 内无后续滚动事件时，认为完成了一次滚动
    startTime: 0
  }
  _canSendFlag = true // 是否允许上报
  pageActiveListenerTimer = null // 页面活动状态定时器
  pageActiveListenerInterval = 10000 // 页面活动状态时间间隔

  get canSendFlag() {
    return this._canSendFlag
  }

  set canSendFlag(value) {
    if (value !== this._canSendFlag) {
      if (value) {
        console.log('恢复数据上报')
        this._canSendFlag = value

        this.trackEvent(TrackEventBackend.pageActiveRestart)
      } else {
        console.log('长时间未操作，停止数据上报')
        this.trackEvent(TrackEventBackend.pageActiveStop)
      }
    }

    this._canSendFlag = value
  }

  /**
   * 埋点
   * @param {*} options // 配置
   * @param {*} options.customParams // 通用埋点数据
   * @param {*} options.customFnMap // 获取自定义参数方法
   * @param {*} options.counterOptions // 计数器配置 {elementName: [eventType, eventType]}
   * @param {*} options.pageLoadDelayFlag // 是否在页面中手动调用 trackPageLoadEvent 方法，不设置时会多次触发 page_load 事件
   */
  constructor(pageType, options = {}) {
    this.basicParams.page_type = pageType
    this.options = options
    this.setCustomFnMap(options.customFnMap)
    this.setCustomParams(options.customParams)
    // 初始化计数器
    this.initCounter(options.counterOptions)
    this.init()
  }

  async init() {
    // 初始化数据队列
    this.initTrackerQueue()
    // 初始化基础信息
    this.initBasicParams()
    await this.initBasicParamsAsync()
    // 初始化事件监听器
    this.initEvent()
  }

  // 设置 customParams
  setCustomParams(customParams) {
    this.customParams = customParams
  }

  // 设置 customFnMap
  setCustomFnMap(customFnMap) {
    this.customFnMap = customFnMap
  }

  /**
   *监听元素曝光事件
   * optionsOrOptionsList 可传单个 options 也可传多个
   * @param {*} vmInstance // vue 实例
   * @param {*} options.selector // 元素选择器
   * @param {string} options.elementName // 元素名称
   * @param {function} options.getParams // 获取额外参数的方法
   */
  addListenerElementView(vmInstance, optionsOrOptionsList) {
    if (!Array.isArray(optionsOrOptionsList)) {
      optionsOrOptionsList = [optionsOrOptionsList]
    }

    if (!optionsOrOptionsList) {
      return
    }

    optionsOrOptionsList.forEach(options => {
      const elementName = options.elementName
      const selector = options.selector

      if (!elementName || !selector) {
        console.error(`监听元素曝光失败：缺少必要参数`)
        return
      }

      const observer = uni.createIntersectionObserver(vmInstance, {initialRatio: 0})

      observer
        .relativeToViewport({
          top: 0
        })
        .observe(selector, ({intersectionRatio}) => {
          const visible = intersectionRatio !== 0

          let params = {}

          if (typeof options.getParams === 'function') {
            params = options.getParams()
          }

          if (visible) {
            this.trackElementViewEvent(elementName, params)
          } else {
            this.trackElementHideEvent(elementName, params)
          }
        })
      elementViewObserverMap[elementName] = observer
    })
  }
  // 初始化计数器
  initCounter(counterOptions) {
    for (let elementName in counterOptions) {
      const eventTypes = counterOptions[elementName]

      eventTypes.forEach(eventType => {
        this.addCounter(elementName, eventType)
      })
    }
  }

  /**
   * 新增计数器
   * @param {*} elementName
   * @param {*} eventType
   */
  addCounter(elementName, eventType) {
    // 支持计数器的事件
    const counterEventMap = {
      [TrackEvent.areaVisit]: TrackEventBackend.areaVisitEntry,
      [TrackEvent.elementView]: TrackEventBackend.elementView,
      [TrackEvent.click]: TrackEventBackend.click
    }
    // 优先转换为通用事件，无法转换时使用原始 eventType 以支持自定义事件
    const backendEventType = counterEventMap[eventType] || eventType
    const key = `${elementName}-${backendEventType}`

    if (this.counterMap[key] === undefined) {
      this.counterMap[key] = 0
    }
  }

  // 初始化数据队列
  initTrackerQueue() {
    this.trackerQueue = new TrackerQueue({
      send: this.send.bind(this)
    })
  }

  // 初始化基础数据
  initBasicParams() {
    // 页面信息
    const currentPageInfo = getCurrentPageInfo()

    if (currentPageInfo) {
      this.basicParams['page_url'] = currentPageInfo.route
      this.basicParams['page_params'] = currentPageInfo.options
    }

  }

  // 初始化需异步获取的基础数据
  async initBasicParamsAsync() {

  }

  // 初始化事件
  initEvent() {
    // 触发 pv 事件
    this.trackPageViewEvent()
    // // 事件委托-click
    // window.addEventListener('click', this.handleClick.bind(this), true)
    // // 事件委托-beforeunload
    // window.addEventListener('beforeunload', this.handleBeforeunload.bind(this), true)
    // // 初始化性能监控事件
    // this.initPerformanceEvent()
    // 初始化元素曝光事件
    // this.initListenerElementView()
    // // 事件委托-scroll
    // window.addEventListener('scroll', this.handleScroll.bind(this), true)
  }

  // 初始化页面活动状态
  initPageActiveListener() {
    this.startPageActiveListener()

    document.addEventListener('visibilitychange', () => {
      // 页面隐藏时停止上报
      this.canSendFlag = !document.hidden

      if (document.hidden) {
        this.stopPageActiveListener()
      } else {
        this.startPageActiveListener()
      }
    })
  }

  startPageActiveListener() {
    this.stopPageActiveListener()

    this.canSendFlag = true
    this.pageActiveListenerTimer = setTimeout(() => {
      this.canSendFlag = false
    }, this.pageActiveListenerInterval)
  }

  stopPageActiveListener() {
    clearTimeout(this.pageActiveListenerTimer)
  }

  // 初始化性能监控事件
  initPerformanceEvent() {
    window.addEventListener('load', () => {
      this.trackPageLoadEvent(null, true)
      this.initPageActiveListener()
    })
  }

  // 页面离开事件处理函数
  async handleBeforeunload() {
    this.destroyFlag = true
    this.trackEvent(TrackEventBackend.pageLeave)

    if (this.trackerQueue) {
      this.trackerQueue.destroy()
    }

    if (Object.key(elementViewObserverMap).length > 0) {
      for (let key in elementViewObserverMap) {
        elementViewObserverMap[key].disconnect()
        delete elementViewObserverMap[key]
      }
    }
  }

  // 滚动事件处理函数
  async handleScroll(scrollTop) {
    if (this.scrollInfo.scrollTop === scrollTop) {
      return
    }

    this.startPageActiveListener()

    clearTimeout(this.scrollInfo.timer)

    this.scrollInfo.timer = setTimeout(() => {
      const params = {
        scrollTop,
        distance: Math.abs(this.scrollInfo.scrollTop - scrollTop),
        direction: this.scrollInfo.scrollTop - scrollTop > 0 ? 'top' : 'bottom',
        duration: Date.now() - this.scrollInfo.startTime
      }

      this.trackEvent(TrackEventBackend.pageScroll, null, params)

      this.scrollInfo.scrollTop = scrollTop
      this.scrollInfo.flag = true
    }, this.scrollInfo.duration)

    if (this.scrollInfo.flag) {
      this.scrollInfo.flag = false
      this.scrollInfo.startTime = Date.now()
    }
  }

  // 处理点击事件处理函数
  async handleClick(dataset) {
    const trackData = await this.getTrackData(dataset, TrackEvent.click)
    if (trackData) {
      this.trackClickEvent(trackData.elementName, trackData.params)
    }

    // const target = event.target

    // this.startPageActiveListener()

    // // click 事件
    // // 向上查找最近的带有埋点属性的元素
    // const trackClickNode = findTrackNode(target, TrackEvent.click)

    // if (trackClickNode) {
    //   const trackData = await this.getTrackData(trackClickNode, TrackEvent.click)
    //   if (trackData) {
    //     this.trackClickEvent(trackData.elementName, trackData.params)
    //   }
    // }

    // // areaVisit 事件
    // const trackAreavisitNode = findTrackNode(target, TrackEvent.areaVisit)
    // let trackAreavisitCurrentElementName = ''

    // if (trackAreavisitNode) {
    //   const trackData = await this.getTrackData(trackAreavisitNode, TrackEvent.areaVisit)
    //   trackAreavisitCurrentElementName = trackData.elementName

    //   // areaVisitEntry 事件
    //   if (trackData && !this.areaVisitEntryMap[trackData.elementName]) {
    //     // 不存在进入事件
    //     trackData.timestamp = Date.now()
    //     this.areaVisitEntryMap[trackData.elementName] = trackData
    //     this.trackEvent(TrackEventBackend.areaVisitEntry, trackData.elementName, trackData.params)
    //   }
    // }

    // // areaVisitLeave 事件
    // if (Object.keys(this.areaVisitEntryMap).length > 0) {
    //   for (let prop in this.areaVisitEntryMap) {
    //     if (prop !== trackAreavisitCurrentElementName) {
    //       // 在区域内的点击不会触发 leave 事件
    //       const trackData = this.areaVisitEntryMap[prop]
    //       this.areaVisitEntryMap[trackData.elementName] = trackData
    //       trackData.params.duration = Date.now() - trackData.timestamp
    //       this.trackEvent(TrackEventBackend.areaVisitLeave, trackData.elementName, trackData.params)
    //       delete this.areaVisitEntryMap[prop]
    //     }
    //   }
    // }
  }

  // 获取埋点数据
  async getTrackData(dataset, type) {
    try {
      const elementName = dataset[type]

      // 处理动态参数
      const dynamicParams = getDynamicParams(dataset)
      const customFnParams = await this.getCustomFnParams(dataset)

      return {
        elementName,
        params: {
          ...dynamicParams,
          ...customFnParams
        }
      }
    } catch (error) {
      console.error('获取埋点数据失败 getTrackData:', error)
      return null
    }
  }

  // 获取计数器参数
  getCounterParams(elementName, eventType) {
    const params = {}
    const key = `${elementName}-${eventType}`

    if (this.counterMap[key] !== undefined) {
      params[this.counterKey] = ++this.counterMap[key]
    }
    return params
  }

  // 获取自定义参数（通过函数）
  async getCustomFnParams(dataset) {
    const customFn = dataset['trackFn']
    if (customFn && this.customFnMap[customFn] && typeof this.customFnMap[customFn] === 'function') {
      try {
        return await this.customFnMap[customFn]()
      } catch (error) {
        console.error('获取自定义参数失败 getCustomFnParams:', error)
        return {}
      }
    }
    return {}
  }

  // --------------------------------------------- 触发上报事件 ---------------------------------------------
  // 触发元素曝光事件
  trackElementViewEvent(elementName, eventData = {}) {
    this.elementViewMap[elementName] = {
      timestamp: Date.now()
    }
    this.trackEvent(TrackEventBackend.elementView, elementName, eventData)
  }

  // 触发元素隐藏事件
  trackElementHideEvent(elementName, eventData = {}) {
    const info = this.elementViewMap[elementName]
    // 元素曝光后才允许触发隐藏事件

    if (info) {
      eventData.duration = Date.now() - info.timestamp
      this.trackEvent(TrackEventBackend.elementHide, elementName, eventData)
      delete this.elementViewMap[elementName]
    }
  }

  // 触发点击事件
  trackClickEvent(elementName, eventData = {}) {
    this.trackEvent(TrackEventBackend.click, elementName, eventData)
  }

  // 触发 pv 事件
  trackPageViewEvent() {
    this.trackEvent(TrackEventBackend.pageView)
  }

  /**
   *  触发 pageLoad 事件
   *  外部调用时，需要配置 pageLoadDelayFlag 确保该方法在 onload 事件之后触发
   *  @param {*} eventData
   *  @param {*} isLoad
   */
  trackPageLoadEvent(eventData = {}, isLoad = false) {
    if (isLoad) {
      // 由 onLoad 触发
      this.performanceInfo = getPerformanceInfo()

      if (this.pageLoadParams || !this.options.pageLoadDelayFlag) {
        // 已被外部触发过 || 无需等待外部触发
        this.trackPageLoadEventInner()
      }
    } else {
      // 外部触发
      this.pageLoadParams = Object.assign({}, eventData, {
        performance: {
          page_load: Date.now() - performance.timing.navigationStart
        }
      })

      if (this.performanceInfo) {
        // onload 已触发
        this.trackPageLoadEventInner()
      }
    }
  }

  trackPageLoadEventInner() {
    const params = Object.assign({}, this.pageLoadParams, {
      performance: Object.assign({}, this.performanceInfo, this.pageLoadParams?.performance)
    })

    this.trackEvent(TrackEventBackend.pageLoad, '', params)
  }

  trackSubmitEvent(elementName, result, errMsg, isBackend = false, otherParams = {}) {
    this.trackClickEvent(elementName, {
      result,
      err_msg: errMsg,
      err_msg_from: isBackend ? 'backend' : 'frontend',
      ...otherParams
    })
  }

  trackLogEvent(params) {
    this.trackEvent(TrackEventBackend.log, '', params)
  }

  /**
   * 触发上报通用方法
   * @param {*} elementName
   * @param {*} eventData
   */
  trackEvent(eventType, elementName, eventData = {}) {
    const counterParams = this.getCounterParams(elementName, eventType)
    const data = {
      event_type: eventType,
      element_name: elementName,
      ...this.customParams,
      // ...eventData,
      ...this.basicParams,
      ext_params: Object.assign({}, eventData, counterParams)
    }

    if (this.canSendFlag || [TrackEventBackend.pageLeave, TrackEventBackend.pageActiveStop].includes(eventType)) {
      this.trackerQueue.push(data)
    }
  }
  // 发送埋点数据
  async send(data) {
    // 调用数据上报接口
    console.table(
      data.map(item => ({
        element_name: item.element_name,
        event_type: item.event_type,
        ext_params: JSON.stringify(item.ext_params)
      })),
      ['element_name', 'event_type', 'ext_params']
    )
    console.log('埋点数据上报：（索引）', data)

    // let requestUrl = `xxxx`

    // Http.post(requestUrl, data, {
    //   header: {
    //     'content-type': 'application/json' // json 格式
    //   }
    // })
  }
}
